﻿/**************************************************************
 * St. Sana - A file/folder scanner originall designed for    *
 * Open Media Library (open-media-library.googlecode.com)     *
 *                                                            *
 * Available for use by ANY project that wants to use it.     *
 * St. Sana is covered by the Apache 2 license.               *
 * Feel free to use this software however you like so as long *
 * as you follow the guidelines of above mentioned license.   *
 * Written by David J. Shultz (djshultz@gmail.com)            *
 **************************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.VisualBasic;
using System.Threading;

namespace StSana
{
    public class Servant
    {
        public delegate void SSEventHandler(string message);
        public event SSEventHandler Log;
        protected void FireEvent(string message)
        {
            if (Log != null)
                Log(message);
        }
        /// <summary>
        /// private IList of Entity objects.
        /// </summary>
        IList<Entity> entities;
        /// <summary>
        /// private IList of strings, each string is a full path to be scanned.
        /// </summary>
        IList<string> basePaths;
        IList<SSFile> _filesFound;
        IList<SSFolder> _masterFolders;

        /// <summary>
        /// Entities holds a list of all Entity objects found during a Scan() or ScanFolder() call.
        /// </summary>
        public IList<Entity> Entities
        {
            get { return entities; }
        }

        /// <summary>
        /// Servant: empty constructor
        /// </summary>
        public Servant()
        {
            entities = new List<Entity>();
            basePaths = new List<string>();
        }

        /// <summary>
        /// Servant: Constructor given an array string sets up the ScanPaths that will be used by a call to Scan()
        /// </summary>
        /// <param name="pathsToScan">A list of full path strings to scan</param>
        public Servant(string[] pathsToScan)
        {
            entities = new List<Entity>();
            basePaths = new List<string>();

            foreach (string path in pathsToScan)
            {
                if (Directory.Exists(path))
                    basePaths.Add(path);
            }
        }

        /// <summary>
        /// Adds a new full path string to the list of paths to be scanned
        /// </summary>
        /// <param name="newBasePath">A full path string to be scanned</param>
        public void AddBasePath(string newBasePath)
        {
            if (!basePaths.Contains(newBasePath))
                basePaths.Add(newBasePath);
        }

        /// <summary>
        /// Public getter for an IList of basepaths
        /// </summary>
        public IList<string> BasePaths
        {
            get { return basePaths; }
        }

        private string LocalFolderName(string folder)
        {
            return folder.Substring(folder.LastIndexOf(Path.DirectorySeparatorChar) + 1);
        }

        public void Scan()
        {
            FireEvent("Beginning Scan");
            _masterFolders = new List<SSFolder>();
            _filesFound = new List<SSFile>();
            int level = 0;
            #region buildDOM
            FireEvent("Building Folder Context Engine");
            foreach (string path in basePaths)
            {
                SSFolder _subFolder = _BuildDOM(path, level);
                if (_subFolder != null)
                {
                    _subFolder.Level = level;
                    _masterFolders.Add(_subFolder);
                }
            }
            #endregion buildDOM

            #region process folders
            FireEvent("Beginning Disc based Processing");
            foreach (SSFolder folder in _masterFolders)
                ProcessFolder(folder);

            #endregion process folders

            #region process files
            FireEvent("Beginning file based Processing");
            foreach (SSFile file in _filesFound)
            {
                if (!file.IsProcessed) // skip it if it was picked up and process by some other file
                {
                    if (Serf.IsValidVideoExtension(Path.GetExtension(file.Name).Substring(1)))
                    {
                        // is it a tv episode?
                        if (Serf.IsValidTVEpisode(file.Name))
                        {
                            //  what season, episode, show?
                            Serf.EpisodeData ed = Serf.getEpisodeDataForPath(file.Name);
                            if (ed.SeasonNumber > -1 && ed.EpisodeNumber.Count > -1)
                            {
                                FleshOutEpisodeInfo(file, ed);
                                FireEvent("Adding " + file.Name);
                                AddFileToEntities(file);
                                file.ParentFolder.AppearsToContainATVShow = true;
                            }
                        }
                        else // ok, its not a tv show, what is it?
                        {
                            Video vid = new Video();
                            vid.Name = file.ParentFolder.Level >= 1 ? file.ParentFolder.Name : Path.GetFileNameWithoutExtension(file.Name);
                            vid.AddFile(file.FullPath);

                            if (file.ParentFolder.Level >= 1)
                            {
                                IList<SSFile> siblings = file.SiblingFiles();
                                if (siblings.Count > 0)
                                    ProcessSiblingFiles(file, siblings);

                                foreach (SSFile siblingFile in siblings)
                                {
                                    vid.AddFile(siblingFile.FullPath);
                                    siblingFile.IsProcessed = true;
                                }
                            }

                            Entity movieEntity = null;
                            Series movieSeries = null;

                            if (file.ParentFolder.ParentFolder != null && file.ParentFolder.ParentFolder.SiblingFolders().Count > 0 && file.ParentFolder.Level > 1)
                            {
                                movieEntity = Entity.FindOrCreateForName(entities, file.ParentFolder.ParentFolder.Name);
                                movieEntity.EntityType = Serf.EntityType.COLLECTION;
                            }
                            else if (file.ParentFolder.Level >= 1)
                            {
                                movieEntity = Entity.FindOrCreateForName(entities, file.ParentFolder.Name);
                                movieEntity.EntityType = Serf.EntityType.MOVIE;
                            }
                            else
                            {
                                movieEntity = Entity.FindOrCreateForName(entities, Path.GetFileNameWithoutExtension(file.Name));
                                movieEntity.EntityType = Serf.EntityType.MOVIE;
                            }

                            int index = movieEntity.ContainsSeries(file.ParentFolder.Name);
                            if (index > -1)
                                movieSeries = movieEntity.Series[index];
                            else
                            {
                                movieSeries = new Series();
                                movieSeries.Name = file.ParentFolder.Level >= 1 ? file.ParentFolder.Name : Path.GetFileNameWithoutExtension(file.Name);
                                movieEntity.Add(movieSeries);
                            }


                            file.ParentFolder.AppearsToContainAMovie = true;
                            FireEvent("Adding " + vid.Name);
                            movieSeries.Add(vid);

                            if (!entities.Contains(movieEntity))
                                entities.Add(movieEntity);
                        }

                        // is it a movie?
                        //  does it have sibling files? (is the movie broken into multiple files?)
                        //  is it part of a collection?
                        //  (i.e. sibling theaters contain movies AND parents siblings subfolders also contain movies)
                    }

                    //Console.WriteLine("Processing file: {0}", file.Name);
                }
            }
            #endregion process files

            // ok the files are done, lets go back and check all the folders for cool stuff like valid folder discs
        }

        private void ProcessFolder(SSFolder folder)
        {
            if (folder.IsDiscFolder)
            {
                Video vid = new Video();
                vid.Name = folder.Name;
                vid.AddFile(folder.FullPath);

                Entity movieEntity = null;
                Series movieSeries = null;

                if (folder.ParentFolder != null && folder.ParentFolder.SiblingFolders().Count > 0 && folder.Level > 1)
                {
                    movieEntity = Entity.FindOrCreateForName(entities, folder.ParentFolder.Name);
                    movieEntity.EntityType = Serf.EntityType.COLLECTION;
                    FireEvent(folder.FullPath + " appears to be a collection.");
                }
                else
                {
                    movieEntity = Entity.FindOrCreateForName(entities, folder.Name);
                    movieEntity.EntityType = Serf.EntityType.MOVIE;
                    FireEvent(folder.FullPath + " appears to be a movie.");
                }

                int index = movieEntity.ContainsSeries(folder.Name);
                if (index > -1)
                    movieSeries = movieEntity.Series[index];
                else
                {
                    movieSeries = new Series();
                    movieSeries.Name = folder.Name;
                    movieEntity.Add(movieSeries);
                }

                FireEvent("Adding Movie: " + movieSeries.Name);
                movieSeries.Add(vid);
                if (!entities.Contains(movieEntity))
                    entities.Add(movieEntity);
            }
            // process any subfolders that might exist
            foreach (SSFolder subFolder in folder.SubFolders())
            {
                if (subFolder.Name.ToUpperInvariant().Contains("VIDEO_TS"))
                    return;
                if (subFolder.Name.ToUpperInvariant().Contains("HVDVD_TS"))
                    return;
                if (subFolder.Name.ToUpperInvariant().Contains("BDMV"))
                    return;
                ProcessFolder(subFolder);
            }
        }

        private static void FleshOutEpisodeInfo(SSFile file, Serf.EpisodeData ed)
        {
            file.SeasonNumber = ed.SeasonNumber;
            file.IsATVEpisode = true;
            foreach (int epNo in ed.EpisodeNumber)
                file.EpisodeNumbers.Add(epNo);
            // time to get the show name
            string parentName = file.ParentFolder.Name.ToUpperInvariant();
            if (parentName.Contains("season".ToUpperInvariant()) ||
                Microsoft.VisualBasic.Information.IsNumeric(parentName))
            {
                // parent folder is a season folder, go up another level
                parentName = file.ParentFolder.ParentFolder.Name;
                file.ShowName = parentName;
            }
            else
                file.ShowName = file.ParentFolder.Name;

            file.IsProcessed = true;
        }

        private void AddFileToEntities(SSFile file)
        {
            // ok, lets get this party started!
            Video vid = new Video();
            if (file.IsATVEpisode)
            {
                if (file.SeasonNumber == 0)
                    vid.IsNonStandardVideo = true;

                foreach (int epNo in file.EpisodeNumbers)
                    if (epNo == 0)
                        vid.IsNonStandardVideo = true;
            }
            vid.AddFile(file.FullPath);
            vid.Name = file.Name;
            foreach (int i in file.EpisodeNumbers)
                vid.EpisodeNumbers.Add(i);

            Entity ent = Entity.FindOrCreateForName(entities, file.ShowName);
            ent.EntityType = Serf.EntityType.TV_SHOW;
            Series mySeries = null;
            foreach (Series ser in ent.Series)
                if (ser.Number == file.SeasonNumber)
                    mySeries = ser;

            if (mySeries == null)
            {
                mySeries = new Series();
                mySeries.Number = file.SeasonNumber;
                mySeries.Name = "Season " + file.SeasonNumber;
                ent.Add(mySeries);
            }

            mySeries.Add(vid);
            if (!entities.Contains(ent))
                entities.Add(ent);
        }

        private static void ProcessSiblingFiles(SSFile file, IList<SSFile> siblings)
        {
            string mySubName = Path.GetFileNameWithoutExtension(file.Name);
            foreach (SSFile sibling in siblings)
            {
                string mySiblingName = Path.GetFileNameWithoutExtension(sibling.Name);
                if (mySubName.Length == mySiblingName.Length)
                {
                    // they are the same length of filenames, they might be cousins
                    if (mySubName.Substring(0, mySubName.Length - 4).CompareTo(mySiblingName.Substring(0, mySiblingName.Length - 4)) == 0)
                    {
                        // ok, not counting the last 4 characters (usually something like cd1, cd2, cd01, cd02, etc)
                        file.AddSister(sibling);
                        sibling.AddSister(file);
                        sibling.IsProcessed = true; // we know what this is, set is to already processed.
                    }
                    else
                    {
                        // well they are not exactly the same (in name anyway)
                        // maybe one of them is a trailer or something?
                    }
                }
            }
        }

        private bool IsDiscFolder(SSFolder folder)
        {
            if (Serf.ContainsBDMVSubFolder(folder.FullPath))
            {
                FireEvent(folder.FullPath + " is a ripped Bluray Disc.");
                return true;
            }

            if (Serf.ContainsBlurayFilesWithoutBDMVFolder(folder.FullPath))
            {
                FireEvent(folder.FullPath + " is a ripped Bluray Disc (without proper folder structure)");
                return true;
            }

            if (Serf.ContainsDVDFilesWithoutVideoTSFolder(folder.FullPath))
            {
                FireEvent(folder.FullPath + " is a ripped DVD Disc (without proper folder structure)");
                return true;
            }

            if (Serf.ContainsHDDVDSubFolder(folder.FullPath))
            {
                FireEvent(folder.FullPath + " is a ripped HDDVD Disc.");
                return true;
            }

            if (Serf.ContainsVideoTSSubFolder(folder.FullPath))
            {
                FireEvent(folder.FullPath + " is a ripped DVD Disc.");
                return true;
            }

            return false;
        }

        private SSFolder _BuildDOM(string path, int level)
        {
            if (path.ToUpperInvariant().Contains(".svn".ToUpperInvariant()))
                return null;

            SSFolder topFolder = null;
            if (Directory.Exists(path))
            {
                FireEvent("Reading " + path);
                topFolder = new SSFolder(path);
                if (IsDiscFolder(topFolder)) {
                    topFolder.IsDiscFolder = true;
                    topFolder.DiscType = Serf.getDiscType(topFolder.FullPath);
                    return topFolder; // this blocks us from checking for other files inside a folder that we determine to be a disc folder
                }

                string[] subfolders = Directory.GetDirectories(topFolder.FullPath);
                foreach (string subfolder in subfolders)
                {
                    SSFolder _subfolder = _BuildDOM(subfolder, level +1);
                    if (_subfolder != null)
                    {
                        _subfolder.Level = level + 1;
                        foreach (SSFolder sibling in topFolder.SubFolders())
                        {
                            sibling.AddSibling(_subfolder);
                            _subfolder.AddSibling(sibling);
                        }
                        _subfolder.ParentFolder = topFolder;
                        topFolder.AddFolder(_subfolder);
                    }
                }

                if (!topFolder.IsDiscFolder)
                {
                    foreach (string file in Directory.GetFiles(topFolder.FullPath))
                    {
                        string extension = Path.GetExtension(file).Substring(1);
                        if (Serf.IsValidVideoExtension(extension))
                        {
                            SSFile _file = new SSFile(file, level + 1);
                            if (_file != null)
                            {
                                foreach (SSFile sibling in topFolder.LocalFiles())
                                {
                                    sibling.AddSibling(_file);
                                    _file.AddSibling(sibling);
                                }
                                _file.ParentFolder = topFolder;
                                _filesFound.Add(_file);
                                topFolder.AddFile(_file);
                            }
                        }
                    }
                }
            }
            return topFolder;
        }
    }
}
